OpenBlocks IoT BX1でTI製センサータグ(CC2650)のセンサーデータ(10種類)を取得する
こんにちは、せーのです。今日もOpenBlocks IoT BX1を使って色々あそんでみましょう。
センサータグのデータ種類
さて、今回使うのはこちら。
これはTEXAS INSTRUMENTS社より出ている[Simple Link(CC2650)]というセンサータグです。こちらはマクニカさんからお借りしました。ありがとうございます! こんな小さいのにこのタグから
- ambientTemperature: 周囲温度(気温)
- objectTemperature: 物体の温度(IR温度)
- humidity: 湿度
- barometricPressure: 気圧
- accelerometer: 加速度計(9軸)
- gyroscope: ジャイロスコープ(傾き)
- magnetometer: 磁力計
- luxometer: 光量
という様々な環境データを取得することが出来ます。 通信はBluetooth Low Energy(BLE)になります。今回はアドレスを指定してペアリングしますが、コードの書き方次第では複数のタグを自動でペアリングさせることも可能かと思います。今度試してみます。
BX1側の準備
それでは早速やってみましょう。前提としてBX1がインターネットに繋がっている状態とします。そこまでいっていない方は以下の記事を参考にして下さい。
さて、準備としてBX1にインストールするものは以下となります。
- nvm,node,npm > node 0.11以上
- curl(入って無ければ) > curl 7.18以上
- libbluetooth-dev >= 4.101以上
- jq >= 1.3以上。apt-getにはjqパッケージがないのでsource.listにdebを追加して対応する。
※以下は今回使いませんが次回以降使うので今入れておきます
- pip, aws-cli >= aws-cli 1.7.19以上
- aws sdk for JavaScript > 最新版
これらをひたすら続々入れていきます。その前にntpの設定やlocaleの設定も一応やっちゃいます。Web UI画面でこれらを設定している方は飛ばしてしまって構いません。
vi /etc/apt/source.list
#jqパッケージ用に↓を追記する deb http://ftp.jp.debian.org/debian wheezy-backports main contrib non-free
apt-get update apt-get install -y ntpdate ntp ntpdate ntp1.jst.mfeed.ad.jp apt-get install -y locales sed -i 's/^# ja_JP.UTF-8 UTF-8/ja_JP.UTF-8 UTF-8/' /etc/locale.gen dpkg-reconfigure -f noninteractive locales apt-get install -y curl curl https://raw.githubusercontent.com/creationix/nvm/v0.24.0/install.sh | bash source ~/.nvm/nvm.sh nvm install v0.12.2 nvm alias default v0.12.2 echo -e "\nexport NVM_DIR=\"$NVM_DIR\"\n[ -s \"\$NVM_DIR/nvm.sh\" ] && . \"\$NVM_DIR/nvm.sh\" >> ~/.bashrc apt-get install -y libbluetooth-dev apt-get install -y jq curl -kL https://raw.github.com/pypa/pip/master/contrib/get-pip.py | python pip install awscli
作業ディレクトリの作成
次にセンサーの取得スクリプトを書く作業ディレクトリを作ります。名前は何でも構いません。
mkdir sensortest | cd $_
作業ディレクトリを作ったらセンサーの取得ライブラリをインストールします。 ここで次回以降に使うmoment-timezoneというタイムゾーンの管理ライブラリとAWS SDK for Node.jsも入れておきます。
npm install sandeepmistry/node-sensortag moment-timezone aws-sdk
ソースを書いていく
それではソースを書いていきます。ファイル名は何でも構いません。大きく分けると「接続部分」と「取得部分」の2つに分かれます。
接続部分
まずは接続部分です。ライブラリにはdiscoverByAddress(アドレスから取得)、discoverById(IDから取得)、discoverByUUID(UUIDから取得)という3種類の接続方法とdiscoverというこれらのラッパー関数が用意されています。今回はこの中からdiscoverByAddressを使って接続してみます。
センサータグのアドレスはどうやって調べたらいいのでしょう。これはBX1のWebUIからか、もしくはコンソールから調べることができます。
まずはBX1のWebUIにログインします。
次にBluetoothが有効になっていることを確認し
Bluetoothのペアリングの画面からBLE(下の方のボタン)の検索ボタン[detecting]をクリックします。センサータグが見つかると画面の方にセンサータグの情報が出てきます。
見つからないという場合、センサータグは一定時間通信を行っていないと低電力モードになるので電源ボタンを押すと表示されるかと思います。
コンソールではhcitoolというコマンドを使用します。まずはデバイスを確認します。
hciconfig hci0: Type: BR/EDR Bus: UART BD Address: 98:4F:EE:04:A8:72 ACL MTU: 1021:8 SCO MTU: 64:1 UP RUNNING PSCAN RX bytes:42009 acl:0 sco:0 events:1465 errors:0 TX bytes:1821 acl:0 sco:0 commands:79 errors:0
rfkill list 2: bcm43xx Bluetooth: Bluetooth Soft blocked: no Hard blocked: no 3: phy1: Wireless LAN Soft blocked: no Hard blocked: no 4: brcmfmac-wifi: Wireless LAN Soft blocked: no Hard blocked: no 5: hci0: Bluetooth Soft blocked: no Hard blocked: no
[hci0]というデバイスが見えていればOKです。見えない場合はEdison側でソフトブロックされている可能性があります。解除コマンドを流します。
bluetooth_rfkill_event & rfkill unblock bluetooth
BLEデバイスのスキャンは[lescan]というコマンドになります。
hcitool lescan LE Scan ... F4:F9:51:E2:E8:78 (unknown) F4:F9:51:E2:E8:78 (unknown) B0:B4:48:B9:59:05 (unknown) B0:B4:48:B9:59:05 CC2650 SensorTag
このアドレスを使用します。
では接続部分のコードを書いていきましょう。こんな感じになります。
var myAddress = process.env["TI_ADDRESS"] || "YOUR_TI_sensor_tag_ADDRESS"; var SensorTag = require('sensortag'); console.info(">> STOP: Ctrl+C or SensorTag power off"); console.info("start"); console.info("waiting for connect from " + myAddress); SensorTag.discoverByAddress(myAddress, function(sensorTag) { console.info("found: connect and setup ... (waiting 5~10 seconds)"); sensorTag.connectAndSetup(function() { sensorTag.readDeviceName(function(error, deviceName) { console.info("connect: " + deviceName); //ここにデータ収集の関数を登録する。 }); }); /* In case of SensorTag PowerOff or out of range when fired `onDisconnect` */ sensorTag.on("disconnect", function() { console.info("disconnect and exit"); process.exit(0); }); });
わりとサラッとしたnodeのコードかと思います。これもわかりやすいライブラリのおかげですね。ちなみにこの接続部分のライブラリはこうなってます。
SensorTag.discoverByAddress = function(address, callback) { address = address.toLowerCase(); var onDiscoverByAddress = function(sensorTag) { if (sensorTag._peripheral.address === address) { SensorTag.stopDiscoverAll(onDiscoverByAddress); callback(sensorTag); } }; SensorTag.discoverAll(onDiscoverByAddress); };
discoverAllというタグの検索メソッドにぶん投げつつ、アドレスを小文字化してチェックして、期待したタグのアドレスであればcallback関数(今回はconnectAndSetup())にチェインしていく、という形です。これ以降の関数も基本的にはこの形です。
データ取得部分
次にデータの取得部分を書きます。どのセンサーデータも基本は全て同じで
- enableメソッドでセンサーデータの使用を宣言
- notifyメソッドで一定間隔ごとに通知(今回は1秒ごとにしています)
- 各センサーのChangeイベントでデータ値を取得する
という流れをネストで書いていきます。では書いていきましょう。ジャイロスコープであればこのように書きます。
function ti_gyroscope(conned_obj) { var period = 1000; // ms conned_obj.enableGyroscope(function() { conned_obj.setGyroscopePeriod(period, function() { conned_obj.notifyGyroscope(function() { console.info("ready: notifyGyroscope"); console.info("notify period = " + period + "ms"); conned_obj.on('gyroscopeChange', function(x, y, z) { console.log('gyro_x: ' + x, 'gyro_y: ' + y, 'gyro_z: ' + z); }); }); }); }); }
温度計であればこのように取ります。
function ti_ir_temperature(conned_obj) { var period = 1000; // ms conned_obj.enableIrTemperature(function() { conned_obj.setIrTemperaturePeriod(period, function() { conned_obj.notifyIrTemperature(function() { console.info("ready: notifyIrTemperature"); console.info("notify period = " + period + "ms"); conned_obj.on('irTemperatureChange', function(objectTemperature, ambientTemperature) { console.log('\tobject temperature = %d °C', objectTemperature.toFixed(1)); console.log('\tambient temperature = %d °C', ambientTemperature.toFixed(1)); }); }); }); }); }
そしてこれらのファンクションを先ほどの接続部分の接続後に登録しておけばOKです。
var myAddress = process.env["TI_ADDRESS"] || "YOUR_TI_sensor_tag_ADDRESS"; var SensorTag = require('sensortag'); console.info(">> STOP: Ctrl+C or SensorTag power off"); console.info("start"); console.info("waiting for connect from " + myAddress); SensorTag.discoverByAddress(myAddress, function(sensorTag) { console.info("found: connect and setup ... (waiting 5~10 seconds)"); sensorTag.connectAndSetup(function() { sensorTag.readDeviceName(function(error, deviceName) { console.info("connect: " + deviceName); //ここにデータ収集の関数を登録する。 ti_gyroscope(sensorTag); ti_ir_temperature(sensorTag); }); }); /* In case of SensorTag PowerOff or out of range when fired `onDisconnect` */ sensorTag.on("disconnect", function() { console.info("disconnect and exit"); process.exit(0); }); });
こんな感じで全種類のセンサーを取る処理がこのようになります。
/* * $ npm install sandeepmistry/node-sensortag ## (require `libbluetooth-dev`) * $ TI_UUID=your_ti_sensor_tag_UUID node this_file.js */ //var myUUID = process.env["TI_UUID"] || "YOUR_TI_sensor_tag_UUID"; var myAddress = process.env["TI_ADDRESS"] || "YOUR_TI_sensor_tag_ADDRESS"; function ti_simple_key(conned_obj) { conned_obj.notifySimpleKey(function() { console.info("ready: notifySimpleKey"); console.info("/* left right (true = pushed, false = released) */"); conned_obj.on("simpleKeyChange", function(left, right) { /* run per pushed button */ console.log(left, right); }); }); } function ti_gyroscope(conned_obj) { var period = 1000; // ms conned_obj.enableGyroscope(function() { conned_obj.setGyroscopePeriod(period, function() { conned_obj.notifyGyroscope(function() { console.info("ready: notifyGyroscope"); console.info("notify period = " + period + "ms"); conned_obj.on('gyroscopeChange', function(x, y, z) { console.log('gyro_x: ' + x, 'gyro_y: ' + y, 'gyro_z: ' + z); }); }); }); }); } function ti_ir_temperature(conned_obj) { var period = 1000; // ms conned_obj.enableIrTemperature(function() { conned_obj.setIrTemperaturePeriod(period, function() { conned_obj.notifyIrTemperature(function() { console.info("ready: notifyIrTemperature"); console.info("notify period = " + period + "ms"); conned_obj.on('irTemperatureChange', function(objectTemperature, ambientTemperature) { console.log('\tobject temperature = %d °C', objectTemperature.toFixed(1)); console.log('\tambient temperature = %d °C', ambientTemperature.toFixed(1)); }); }); }); }); } function ti_accelerometer(conned_obj) { var period = 1000; // ms conned_obj.enableAccelerometer(function() { conned_obj.setAccelerometerPeriod(period, function() { conned_obj.notifyAccelerometer(function() { console.info("ready: notifyAccelerometer"); console.info("notify period = " + period + "ms"); conned_obj.on('accelerometerChange', function(x, y, z) { console.log('\taccel_x = %d G', x.toFixed(1)); console.log('\taccel_y = %d G', y.toFixed(1)); console.log('\taccel_z = %d G', z.toFixed(1)); }); }); }); }); } function ti_humidity(conned_obj) { var period = 1000; // ms conned_obj.enableHumidity(function() { conned_obj.setHumidityPeriod(period, function() { conned_obj.notifyHumidity(function() { console.info("ready: notifyHumidity"); console.info("notify period = " + period + "ms"); conned_obj.on('humidityChange', function(temperature, humidity) { console.log('\ttemperature = %d °C', temperature.toFixed(1)); console.log('\thumidity = %d %', humidity.toFixed(1)); }); }); }); }); } function ti_magnetometer(conned_obj) { var period = 1000; // ms conned_obj.enableMagnetometer(function() { conned_obj.setMagnetometerPeriod(period, function() { conned_obj.notifyMagnetometer(function() { console.info("ready: notifyMagnetometer"); console.info("notify period = " + period + "ms"); conned_obj.on('magnetometerChange', function(x, y, z) { console.log('\tmagnet_x = %d μT', x.toFixed(1)); console.log('\tmagnet_y = %d μT', y.toFixed(1)); console.log('\tmagnet_z = %d μT', z.toFixed(1)); }); }); }); }); } function ti_barometric_pressure(conned_obj) { var period = 1000; // ms conned_obj.enableBarometricPressure(function() { conned_obj.setBarometricPressurePeriod(period, function() { conned_obj.notifyBarometricPressure(function() { console.info("ready: notifyBarometricPressure"); console.info("notify period = " + period + "ms"); conned_obj.on('barometricPressureChange', function(pressure) { console.log('\tpressure = %d mBar', pressure.toFixed(1)); }); }); }); }); } function ti_luxometer(conned_obj) { var period = 1000; // ms conned_obj.enableLuxometer(function() { conned_obj.setLuxometerPeriod(period, function() { conned_obj.notifyLuxometer(function() { console.info("ready: notifyLuxometer"); console.info("notify period = " + period + "ms"); conned_obj.on('luxometerChange', function(lux) { console.log('\tlux = %d', lux.toFixed(1)); }); }); }); }); } var SensorTag = require('sensortag'); console.info(">> STOP: Ctrl+C or SensorTag power off"); console.info("start"); console.info("waiting for connect from " + myAddress); //SensorTag.discoverByUuid(myUUID, function(sensorTag) { SensorTag.discoverByAddress(myAddress, function(sensorTag) { console.info("found: connect and setup ... (waiting 5~10 seconds)"); sensorTag.connectAndSetup(function() { sensorTag.readDeviceName(function(error, deviceName) { console.info("connect: " + deviceName); ti_simple_key(sensorTag); ti_gyroscope(sensorTag); ti_ir_temperature(sensorTag); ti_accelerometer(sensorTag); ti_humidity(sensorTag); ti_magnetometer(sensorTag); ti_barometric_pressure(sensorTag); ti_luxometer(sensorTag); }); }); /* In case of SensorTag PowerOff or out of range when fired `onDisconnect` */ sensorTag.on("disconnect", function() { console.info("disconnect and exit"); process.exit(0); }); });
これを先ほどのアドレスを引数にして流してみます。
TI_ADDRESS=B0:B4:48:B9:59:05 node ti_all.js >> STOP: Ctrl+C or SensorTag power off start waiting for connect from B0:B4:48:B9:59:05 found: connect and setup ... (waiting 5~10 seconds) connect: SensorTag 2.0 ready: notifySimpleKey /* left right (true = pushed, false = released) */ ready: notifyAccelerometer notify period = 1000ms ready: notifyMagnetometer notify period = 1000ms ready: notifyGyroscope notify period = 1000ms ready: notifyIrTemperature notify period = 1000ms ready: notifyHumidity notify period = 1000ms ready: notifyBarometricPressure notify period = 1000ms accel_x = 0 G accel_y = 0 G accel_z = 0 G gyro_x: 0 gyro_y: 0 gyro_z: 0 magnet_x = 0 μT magnet_y = 0 μT magnet_z = 0 μT ready: notifyLuxometer notify period = 1000ms temperature = 27.9 °C humidity = 62.9 % object temperature = 21.7 °C ambient temperature = 27.8 °C pressure = 1009.9 mBar lux = 30
これで各種センサーデータを取ることに成功しました!
まとめ
いかがでしたでしょうか。node部分が慣れてない人にはちょっと引っかかるかもしれませんが全体的には簡単なプログラムでデータが取得できたかと思います。 むしろハマるのは環境設定の方でした。私も最初は環境設定6時間、コーディング30分、という悲しい結果になりました。IoTはここが難しい。 みなさんは是非ハマらずに設定ができることを祈っています。次回はこの取得したデータをKinesisに流してみたいと思います。